package com.cqw.utils;

import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @Author: caoqingwen
 * @CreateTime: 2021-07-27 16:52
 * @Description: Redis分布式锁
 */
@Component
public class RedisLock {


    @Resource
    private RedisTemplate redisTemplate;


    @Resource
    private DefaultRedisScript<Long> defaultRedisScript;

    /**
     * 默认超时时间
     */
    private long defaultTimeOut = 3000L;


    private static final Long RELEASE_SUCCESS = 1L;


    /**
     * 获取锁
     *
     * @param key         锁标识
     * @param expiredTime 过期时间
     * @return
     */
    public boolean tryLock(String key, Long expiredTime) {
        return lock(key, null, expiredTime);
    }


    /**
     * 获取锁
     *
     * @param key         锁标识
     * @param value       线程标识
     * @param expiredTime 过期时间
     * @return
     */
    public boolean tryLock(String key, String value, Long expiredTime) {
        return lock(key, value, expiredTime);
    }


    /**
     * 获取锁
     *
     * @param key 锁标识
     * @return
     */
    public boolean tryLock(String key) {
        return lock(key, null, defaultTimeOut);
    }


    /**
     * 获取锁
     *
     * @param key   锁标识
     * @param value 线程标识
     * @return
     */
    public boolean tryLock(String key, String value) {
        return lock(key, value, defaultTimeOut);
    }


    /**
     * @param key         锁标识
     * @param value       线程标识
     * @param expiredTime 过期时间
     * @return
     */
    public boolean lock(String key, String value, Long expiredTime) {
        long start = System.currentTimeMillis();
        while (true) {
            //检测是否超时
            if (System.currentTimeMillis() - start > expiredTime) {
                return false;
            }
            //执行set命令
            Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, expiredTime, TimeUnit.SECONDS);
            //是否成功获取锁
            if (absent != null && absent) {
                return true;
            }
            return false;
        }
    }


    /**
     * 解锁
     *
     * @param key   锁标识
     * @param value 线程标识
     * @return 解锁状态
     */
    public boolean unlock(String key, String value) {
        //使用Lua脚本：先判断是否是自己设置的锁，再执行删除
        Long result = (Long) redisTemplate.execute(defaultRedisScript, Arrays.asList(key, value));
        //返回最终结果
        return RELEASE_SUCCESS.equals(result);
    }


    /**
     * @return lua脚本
     */
    @Bean
    public DefaultRedisScript<Long> defaultRedisScript() {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
        return defaultRedisScript;
    }


}
